/**
 * OSAmI: Open Source Ambient Intelligence
 * A simple discovery protocol (based on multicast communications) for constrained devices.
 * Copyright (C) Lionel Debroux 2006 (as a TER/Magistere M1 student in the LSR-IMAG-ADELE team, for a demo in the H-Omega project)
 * Copyright (C) Lionel Debroux 2010 (as an engineer in the LCIS-INPG-CTSYS team, for a pre-demo in the FR part of the OSAMI project)
 * Contact: osami-general@osami-commons.org
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation;
 * version 2.1 of the License.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Suite 500 Boston, MA 02110-1335 USA
 */

package fr.imag.adele.homega.impl.unidiscovery;

import java.io.IOException;
import java.util.Dictionary;
import java.util.logging.Logger;

import fr.imag.adele.homega.impl.unicast.UnicastHandler;
import fr.imag.adele.homega.impl.unicast.UnicastHandlerImpl;
import fr.imag.adele.homega.impl.unicast.UnicastSender;
import fr.imag.adele.homega.impl.unicast.UnicastSenderImpl;
import fr.imag.adele.homega.impl.unicast.UnicastReceiverImpl;
import fr.imag.adele.homega.impl.unidiscovery.DeviceMetadata;

/**
 * This class implements the device side of a simple protocol inspired by WS-Discovery (but MUCH easier to implement and EXTREMELY more lightweight !).
 */
public class DeviceImpl
	extends fr.imag.adele.homega.impl.unicast.UnicastReceiverImpl
	implements Device, Comparable {

	private fr.imag.adele.homega.impl.unicast.UnicastSender sender = null;
	private fr.imag.adele.homega.impl.unicast.UnicastHandler handler = null;

	private DeviceMetadata metadata = null;

	private String deviceIPAddr = null;
	private int devicePort = 0;
	private String bridgeIPAddr = null;
	private int bridgePort = 0;

	private String id = null;
	private boolean hasQuit = true;

	public DeviceImpl() {
		
	}

	public DeviceImpl(String deviceName,
			String bridgeIPAddr, int bridgePort,
			String deviceIPAddr, int devicePort,
			String id) throws Exception {
		initialize(deviceName, bridgeIPAddr, bridgePort, deviceIPAddr, devicePort, id);
	}

	public void initialize(String deviceName,
			String bridgeIPAddr, int bridgePort,
			String deviceIPAddr, int devicePort,
			String id) throws Exception {
		sender = new UnicastSenderImpl(deviceName);
		this.name = deviceName;

		setBridgeParms(bridgeIPAddr, bridgePort);
		setDeviceParms(deviceIPAddr, devicePort);

		// Bind to given address.
		sender.bind(deviceIPAddr, 12345);

		hasQuit = false;

		handler = new UnicastHandlerImpl();

		handler.register(bridgeIPAddr, bridgePort, (fr.imag.adele.homega.impl.unicast.UnicastReceiver)this);
		handler.register(deviceIPAddr, devicePort, (fr.imag.adele.homega.impl.unicast.UnicastReceiver)this);

		this.id = id;
		metadata = new DeviceMetadata(id);
	}
	
	private void setBridgeParms(String bridgeIPAddr, int bridgePort) {
		this.bridgeIPAddr = bridgeIPAddr;
		this.bridgePort = bridgePort;
	}

	private void setDeviceParms(String deviceIPAddr, int devicePort) {
		this.deviceIPAddr = deviceIPAddr;
		this.devicePort = devicePort;
	}

	public synchronized void hello() throws IOException {
		sender.send(this.bridgeIPAddr, this.bridgePort, DEVICE_HELLO_PREFIX + this.id + DEVICE_FIELD_SEPARATOR + this.deviceIPAddr + DEVICE_FIELD_SEPARATOR + this.devicePort + DEVICE_FIELD_SEPARATOR);
		Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).info("Sent \u001B[1;31mHELLO\u001B[m message !");
	}

	public synchronized void bye() throws IOException {
		sender.send(this.bridgeIPAddr, this.bridgePort, DEVICE_BYE_PREFIX + this.id + DEVICE_FIELD_SEPARATOR + this.deviceIPAddr + DEVICE_FIELD_SEPARATOR + this.devicePort + DEVICE_FIELD_SEPARATOR);
		Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).info("Sent \u001B[1;31mBYE\u001B[m message !");
	}

	private void replyMetadata() throws IOException {
		sender.send(this.bridgeIPAddr, this.bridgePort, DEVICE_METADATA_PREFIX + metadata.toString());
		Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).info("Sent \u001B[1;31mMETADATA\u001B[m message !");
	}

	public synchronized void addMetadata(String property, String value) {
		try {
			metadata.addMetadata(property, value);
		} catch (Exception e) {
			Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).warning(e.getMessage());
			e.printStackTrace();
			return;
		}
		//Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).info("Metadata " + property + DeviceMetadata.DEVICE_METADATA_KV_SEPARATOR + value + " added !");
	}

	public synchronized void removeMetadata(String property) {
		metadata.removeMetadata(property);
	}

	public String getMetadata(String property) {
		return metadata.getMetadata(property);
	}

	@Override
	public synchronized void receive(String topic, String message) {
		String message2;
		String [] strings;
		String type;

		//Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).info("Device received message \"" + message + "\" !");

		if (message != null) {

			if (message.startsWith(DEVICE_GETMETADATA_PREFIX)) {
				try {
					Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).info("Received \u001B[1;31mGETMETADATA\u001B[m message !");
					// Reply to GET METADATA message.
					replyMetadata();
				} catch (IOException e) {
					Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).warning(e.getMessage());
					e.printStackTrace();
				}
			}
			else if (message.startsWith(DEVICE_PROBE_PREFIX)) {
				message2 = message.substring(DEVICE_PROBE_PREFIX.length());
				strings = message2.split(DEVICE_FIELD_SEPARATOR);

				if (strings.length >= 1) {

					Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).info("Received \u001B[1;31mPROBE\u001B[m message !");

					type = strings[0];
					
					if (   type.equals("*") // Wildcard, all devices must reply.
						   // The discovery bridge is probing for devices of a certain type,
						   // and ours matches.
						|| type.equals(this.getMetadata("Type"))  
						) {
						try {
							this.hello();
						} catch (IOException e) {
							Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).warning(e.getMessage());
							e.printStackTrace();
						}
					}
				}
				else {
					Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).warning("Malformed PROBE message \"" + message + "\"");
				}

			}
			else if (message.startsWith(DEVICE_HELLO_PREFIX)) {
				// Discard hello messages.
			}
			else if (message.startsWith(DEVICE_BYE_PREFIX)) {
				// Discard bye messages.
			}
			else if (message.startsWith(DEVICE_METADATA_PREFIX)) {
				// Discard metadata messages.
			}
			else if (message.startsWith(DEVICE_EVENT_PREFIX)) {
				message2 = message.substring(DEVICE_EVENT_PREFIX.length());
				strings = message2.split(Device.DEVICE_FIELD_SEPARATOR);

				if (strings.length >= 2) {
					id = strings[0];
					/*try {
						id = UUID.fromString(strings[0]);
					}
					catch (IllegalArgumentException e) {
						Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).warning("Malformed EVENT message, incorrect id \"" + strings[0] + "\"");
						return;
					}*/

					Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).info("Received \u001B[1;31mEVENT\u001B[m message from device with id \"" + id + "\" !");


					message2 = strings[1];

					receiveEvent(id, DeviceEvent.parseEvent(message2));
				}
				else {
					Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).warning("Malformed EVENT message \"" + message + "\"");
				}
			}
			else {
				// Not a discovery / eventing message.
				System.out.println("In topic \"" + topic +
						"\", " + name + " received other message:\n\"" + message +"\"");
			}
		}
	}

	protected synchronized void receiveEvent(String id, Dictionary<String,String> dict) {
		// Default implementation: do nothing.
	}

	public synchronized void send(String topic, int port, String message) throws IOException {
		sender.send(topic, port, message);
	}

	public synchronized void sendEvent(DeviceEvent event) throws IOException {
		sender.send(bridgeIPAddr, bridgePort, DEVICE_EVENT_PREFIX + event.toString());
	}

	public void shutdown() {
		try {
			handler.unregister(this.deviceIPAddr, this.devicePort, this);
		} catch (IOException e) {
			Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).warning(e.getMessage());
			e.printStackTrace();
		}
		handler.shutdown();
		if (sender != null) {
			sender.shutdown();
		}

		hasQuit = true;
	}

	public int compareTo(Object arg0) {
		// Shortcut: the ID (e.g. UUID or MAC address) is supposed to be unique...
		return 1;
	}

	/**
	 * Bug catcher...
	 */
	@Override
	public void finalize() {
		if (!hasQuit) {
			System.out.println("\u001B[1;5;31mForgot to terminate the device \"" + name + "\" !\u001B[m");
			shutdown();
		}
	}
}
